using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SolarWinds.Helpers;
using SolarWinds.Input;
using SolarWinds.Layouts;

namespace SolarWinds.Widgets
{
    public abstract class Widget : DrawableComponentManager, IWidgetContainer
    {
        #region Events

        public event EventHandler TabOrderChanged;

        #endregion

        #region Fields
        
        /// <summary>
        /// The cursor used for this widget.
        /// </summary>
        public Texture2D Cursor { get; internal set; }

        /// <summary>
        /// Each widget has a prarent widget. If a widget has been added as a 
        /// child of an other widget (parented) it cannot be added to another
        /// widget. This assures that a widget cannot be twice on the screen.
        /// </summary>
        protected internal Widget mParent;

        /// <summary>
        /// Each Widget can have multiply child widgets. The widget has the 
        /// responsibility to draw and to update all of its children. If we
        /// want to arrange the child widgets in a certain manner, we have to 
        /// use the layout of the parent widget.
        /// </summary>
        protected internal List<Widget> mChildren = new List<Widget>();

        /// <summary>
        /// Specifies if the screen psotion has to be recalculated 
        /// in the next update. 
        /// </summary>
        private bool mScreenPositionDirty;

        private SpriteBatch mSpriteBatch;

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets a value indicating whether the mouse is over this widget.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if mouse is over this widget; otherwise, <c>false</c>.
        /// </value>
        public bool IsMouseOver { get; internal set; }

        /// <summary>
        /// Gets or sets a value indicating whether this widget has keyboard focus.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this widget has keyyboard focus; otherwise, <c>false</c>.
        /// </value>
        public bool HasKeyFocus { get; internal set; }

        /// <summary>
        /// Gets the parent widget.
        /// </summary>
        public Widget Parent
        {
            get { return mParent; }
        }

        /// <summary>
        /// Maximum height of the widget.
        /// </summary>
        private int mMaxHeight = int.MaxValue;

        public int MaxHeight
        {
            get { return mMaxHeight; }
            set
            {
                mMaxHeight = value;

                if (Height > value) Height = value;
            }
        }

        /// <summary>
        /// Minimum height of the widget.
        /// </summary>
        private int mMinHeight;

        public int MinHeight
        {
            get { return mMinHeight; }
            set
            {
                mMinHeight = value;

                if (Height < value) Height = value;
            }
        }
        
        /// <summary>
        /// Maximum width of the widget.
        /// </summary>
        private int mMaxWidth = int.MaxValue;

        public int MaxWidth
        {
            get { return mMaxWidth; }
            set
            {
                mMaxWidth = value;

                if (Width > value) Width = value;
            }
        }

        /// <summary>
        /// Minimum width of the widget. 
        /// </summary>
        private int mMinWidth;
        
        public int MinWidth
        {
            get { return mMinWidth; }
            set
            {
                mMinWidth = value;

                if (Width < value) Width = value;
            }
        }

        /// <summary>
        /// Defines the position of the widgte on the screen
        /// </summary>
        private Point mScreenPosition;

        public Point ScreenPosition
        {
            get { return mScreenPosition; }
        }

        public Vector2 Size
        {
            get { return new Vector2(mRectangle.Width, mRectangle.Height); }
            set
            {
                if (value.X < mMinWidth) value.X = mMinWidth;
                if (value.X > mMaxWidth) value.X = mMaxWidth;
                if (value.Y < mMinHeight) value.Y = mMinHeight;
                if (value.Y > mMaxHeight) value.Y = mMaxHeight;

                mRectangle.Width = (int)value.X;
                mRectangle.Height = (int)value.Y;

                mScreenPositionDirty = true;
            }
        }

        public int Width
        {
            get { return mRectangle.Width; }
            set
            {
                if (value < mMinWidth) value = mMinWidth;
                if (value > mMaxWidth) value = mMaxWidth;

                mRectangle.Width = value;

                mScreenPositionDirty = true;
            }
        }

        public int Height
        {
            get { return mRectangle.Height; }
            set
            {
                if (value < mMinHeight) value = mMinHeight;
                if (value > mMaxHeight) value = mMaxHeight;

                mRectangle.Height = value;

                mScreenPositionDirty = true;
            }
        }

        public Point Position
        {
            get { return new Point(mRectangle.X, mRectangle.Y); }
            set
            {
                mRectangle.X = value.X;
                mRectangle.Y = value.Y;

                mScreenPositionDirty = true;
            }
        }

        /// <summary>
        /// Rectangle of the widget in local coords. 
        /// Defines position, width and height of the widget.
        /// </summary>
        private Rectangle mRectangle;

        public Rectangle BoundingRect
        {
            get { return mRectangle; }
            set
            {
                if (value.Width < mMinWidth) value.Width = mMinWidth;
                if (value.Width > mMaxWidth) value.Width = mMaxWidth;
                if (value.Height < mMinHeight) value.Height = mMinHeight;
                if (value.Height > mMaxHeight) value.Height = mMaxHeight;

                mRectangle = value;

                mScreenPositionDirty = true;
            }
        }

        public Rectangle ScreenRectangle
        {
            get
            {
                return new Rectangle(mScreenPosition.X, mScreenPosition.Y,
                                     mRectangle.Width, mRectangle.Height);
            }
        }

        /// <summary>
        /// The window title.
        /// </summary>
        private String mCaption = "";

        public String Caption
        {
            get { return mCaption; }
            set { mCaption = value; }
        }

        /// <summary>
        /// Color for text drawn by Font.
        /// </summary>
        private Color mTextColor = Color.White;
        
        public Color TextColor
        {
            get { return mTextColor; }
            set { mTextColor = value; }
        }

        /// <summary>
        /// Font used to draw text in the widget.
        /// </summary>
        private SpriteFont mFont;
        
        public SpriteFont Font
        {
            get { return mFont; }
            set 
            {
                if (value!= null &&
                    mFont != value)
                {
                    mFont = value;
                }
            }
        }
        
        /// <summary>
        /// Layout that manages child widgets.
        /// </summary>
        private Layout mLayout;

        public Layout Layout
        {
            get { return mLayout; }
            set
            {
                // link widget and layout
                value.mParent = this;

                mLayout = value;
            }
        }

        private int mTabOrder;

        public int TabOrder
        {
            get { return mTabOrder; }
            set
            {
                if (mTabOrder != value)
                {
                    mTabOrder = value;

                    if (TabOrderChanged != null)
                    {
                        TabOrderChanged(this, EventArgs.Empty);
                    }
                }
            }
        }

        private bool mTabable = true;
        
        public bool Tabable
        {
            get { return mTabable; }
            set { mTabable = value; }
        }

        #endregion

        #region Protected Properties

        protected SpriteBatch SpriteBatch
        {
            get { return mSpriteBatch; }
        }

        #endregion // Properties

        #region Contructor

        protected Widget(Microsoft.Xna.Framework.Game game)
            :base(game)
        {
        }

        #endregion

        #region Add / Remove Children

        public bool AddChild(Widget widget)
        {
            bool added = false;

            if (widget != null &&
                widget.mParent == null &&
                !mChildren.Contains(widget))
            {
                widget.mParent = this;
                Components.Add(widget);

                int index = mChildren.BinarySearch(widget, TabOrderComparer.Default);
                if (index < 0)
                {
                    index = ~index;
                    while ((index < mChildren.Count) &&
                           (mChildren[index].TabOrder == widget.TabOrder))
                    {
                        index++;
                    }

                    mChildren.Insert(index, widget);
                    widget.TabOrderChanged += OnChildTabOrderChanged;
                }
                
                added = true;
            }

            return added;
        }

        void OnChildTabOrderChanged(object sender, EventArgs e)
        {
            var widget = (Widget)sender;
            mChildren.Remove(widget);
            int index = mChildren.BinarySearch(widget, TabOrderComparer.Default);
            if (index < 0)
            {
                index = ~index;
                while ((index < mChildren.Count) && 
                       (mChildren[index].TabOrder == widget.TabOrder))
                {
                    index++;
                }
                
                mChildren.Insert(index, widget);
            }
        }

        public bool RemoveChild(Widget widget)
        {
            bool removed = true;

            if (widget != null &&
                mChildren.Contains(widget))
            {
                removed &= Components.Remove(widget);
                removed &= mChildren.Remove(widget);
                widget.mParent = null;

                widget.TabOrderChanged -= OnChildTabOrderChanged;
            }

            return removed;
        }

        #endregion

        #region Property Convenience Members

        public void Show()
        {
            Visible = true;
        }

        public void Hide()
        {
            Visible = false;
        }

        #endregion

        public bool Contains(Point point)
        {
            return GeometryHelper.Contains(ScreenRectangle, point);
        }

        #region Key Event Handlers

        // Override if neccessary
        protected internal virtual void KeyPressedEvent(KeyEvent evt) { }
        protected internal virtual void KeyReleasedEvent(KeyEvent evt) { }
        protected internal virtual void KeyClickedEvent(KeyEvent evt) { }
        protected internal virtual void KeyFocusInEvent(KeyEvent evt) { }
        protected internal virtual void KeyFocusOutEvent(KeyEvent evt) { }

        #endregion

        #region Mouse Event Handlers

        // Override if neccessary
        protected internal virtual void MousePressEvent(MouseEvent evt) { }
        protected internal virtual void MouseReleaseEvent(MouseEvent evt) { }
        protected internal virtual void MouseClickEvent(MouseEvent evt) { }
        protected internal virtual void MouseDoubleClickEvent(MouseEvent evt) { }
        protected internal virtual void MouseMoveEvent(MouseEvent evt) { }
        protected internal virtual void MouseOverEvent(MouseEvent evt) { }
        protected internal virtual void MouseOutEvent(MouseEvent evt) { }

        #endregion

        #region Drawing

        public sealed override void Draw(GameTime gameTime)
        {
            // we do some culling before we acctually draw
            if (mParent != null &&
                !(mParent.ScreenRectangle.Contains(ScreenRectangle) ||
                  mParent.ScreenRectangle.Intersects(ScreenRectangle)))
            {
                return;
            }

            GraphicsDevice device = GraphicsDevice;

            mSpriteBatch.Begin(SpriteBlendMode.AlphaBlend,
                               SpriteSortMode.Immediate,
                               SaveStateMode.SaveState);

            PaintEvent();

            mSpriteBatch.End();

            // save renderstates
            Rectangle oldRect = device.ScissorRectangle;
            bool oldTestEnable = device.RenderState.ScissorTestEnable;

            // use scissor test to remove overlapping parts of sub widgets
            // TODO use a framewidth or boderwidth instead of hard code
            var newRect = new Rectangle(ScreenPosition.X + 2, ScreenPosition.Y + 16,
                                        Width - 4, Height - 32);

            // set new renderstates
            device.ScissorRectangle = GeometryHelper.Intersection(oldRect, newRect);
            device.RenderState.ScissorTestEnable = true;

            // this draws all child widgets
            base.Draw(gameTime);

            // restore renderstates
            device.RenderState.ScissorTestEnable = oldTestEnable;
            device.ScissorRectangle = oldRect;
        }

        // Override if neccessary
        protected internal virtual void PaintEvent() { }

        #endregion

        #region Updates

        public override void Update(GameTime gameTime)
        {
            if (mScreenPositionDirty)
            {
                UpdateScreenPosition();
            }

            if (mLayout != null)
            {
                mLayout.Update(gameTime);
            }

            // updates all children
            base.Update(gameTime);
        }

        private void UpdateScreenPosition()
        {
            if (mParent != null)
            {
                mScreenPosition.X = mParent.ScreenPosition.X + mRectangle.X;
                mScreenPosition.Y = mParent.ScreenPosition.Y + mRectangle.Y;
            }
            else
            {
                mScreenPosition = new Point(mRectangle.X, mRectangle.Y);
            }
        }

        #endregion

        #region Load / Unload Content

        protected override void LoadContent()
        {
            base.LoadContent();

            if (mFont == null)
            {
                // load the default font
                mFont = Game.Content.Load<SpriteFont>(@"Fonts\Corbel10");
            }

            mSpriteBatch = new SpriteBatch(Game.GraphicsDevice);
        }

        protected override void UnloadContent()
        {
            base.UnloadContent();

            if (mSpriteBatch != null)
            {
                mSpriteBatch.Dispose();
                mSpriteBatch = null;
            }
        }

        #endregion

        #region IWidgetContainer Members

        public int Count
        {
            get { return mChildren.Count; }
        }

        public Widget[] Children
        {
            get { return mChildren.ToArray(); }
        }

        #endregion
    }
}